import { PrinterManager } from './../utils/print/printer/printManager';
import { PrintComponentService } from './../pages/print/templates/printcontainer.component';
import { StoreSvr } from './store';
import { mpjLog } from './../utils/helper/log';
import { ExpeditionOrderDishManage } from './../components/expeditionOrderDishManage';
import { ExpeditionOrder, ExpeditionOrderItem } from './../models/expeditionOrder';
import { PlayAudioService } from './../components/play-audio';
import { ConfigSvr } from './config';
import { PagingInfo } from './appStore';
import { notOperateOnLoading, IMessageBoxContainer, MessageBoxService, GrowlMessageType, HandleExpectionErrorUseGrowMessage } from './../components/messageBox';
import { NodeService, SysSvr } from './system';
import { FNCManager, KdsMessageType } from './../utils/fnc/fncManager';
import { MessageHandleManager } from './../utils/messageHandle/messageHandleManager';
import { OrderMessageHandle } from './../utils/messageHandle/orderMessageHandle';
import { OrderSvr } from './order';
import { Order, Orderitem } from './../models/order';
import { Observable, Subject, BehaviorSubject } from 'rxjs';
import { Injectable } from '@angular/core';
import * as lodash from 'lodash';
import { List } from 'immutable';
import * as mockOrders from "./../../assets/mock-data/orders.json"
import * as delay from 'delay';
import * as R from "ramda";


export interface PagingInfo {
    orders: Array<any>;
    count: {
        next: number,
        previous: number
    };
    actions: {
        nextPage();
        previousPage();
    };
}

function scanObservable<T, R>(seed: Observable<T>, value: Observable<R>, accumulator: (acc: T, curr: R) => T, seedValue?: T): Observable<T> {
    var result = seed.map(v => { return { type: 'seed', value: v } })
        .merge(value.map(v => { return { type: 'scan', value: v } }))
        .scan((acc, v) => {
            if (v.type == 'seed') {
                return accumulator(v.value, null);
            } else {
                return accumulator(acc, v.value);
            }
        }, seedValue).publishReplay(1);
    result.connect();
    return result;
}
function lazyLoad<T>(observable: Observable<T>, loader: () => any): Observable<T> {
    let loaded = false;
    return Observable.defer(() => {
        if (!loaded) {
            Promise.resolve().then(loader).then(() => loaded = true);
        }
        return observable;
    });
}

@Injectable()
export class OrderStore {
    //依赖的业务服务 不需要时处理时应该清除了 todo
    private orderSvr = new OrderSvr();
    private configSvr = new ConfigSvr();
    private orderMessage: Subject<Order> = new Subject<Order>();
    public observable: Observable<Array<Order>>;
    public finishedOrders: Observable<Array<Order>>;
    private _finishedOrders = new Subject<Array<Order>>();
    
    

    getExpeditedExpeditionOrders():Promise<Array<ExpeditionOrder>> {
        return this.orderSvr.getExpeditedExpeditionOrders();
    }
    refreshOrder(predicate) {
        return ((orders, order) => {
            if (!order) return orders.filter(predicate);
            const myStallIds = this.orderSvr.currentKitchenSolutions.map(x => x.id);
            orders = JSON.parse(JSON.stringify(orders));

            const existingOrder = orders.find(x => x.orderId == order.orderId);
            if (!existingOrder) {
                order.items = order.items.filter(item => item.refundQty || !item.pickup || item.pickup == "pickup") 
                order.items.forEach(item => {
                    R.intersection(item.stalls, myStallIds).length ? (<any>item).isLocalStall = true : (<any>item).isLocalStall = false;
                })
                if (lodash.intersection(myStallIds, order.stall).length > 0) {
                    orders = orders.concat(order);
                }
                return orders.filter(predicate);
            }
            existingOrder.customerIdentity = order.customerIdentity;

            const pickupItems = [];
            order.items.forEach(v => {
                const matchingItem = existingOrder.items[lodash.findIndex(existingOrder.items, { id: v.id })];
                if (!matchingItem) {
                    // 叫起的时候 和退菜 item会增加
                    if(v.qty==v.refundQty){
                        pickupItems.push(v);
                    } else if(v.pickup == "pickup"){
                        pickupItems.push(v);
                    }
                    return;
                };
                matchingItem.pickup = v.pickup;
                matchingItem.finishTime = v.finishTime;
                matchingItem.deliverTime = v.deliverTime;
                matchingItem.refundQty = v.refundQty;
                matchingItem.remindCount = v.remindCount;
                matchingItem.qty = v.qty;
                matchingItem.type = v.type;
            });

            //叫起
            pickupItems.forEach(item => {
                R.intersection(item.stalls, myStallIds).length ? (<any>item).isLocalStall = true : (<any>item).isLocalStall = false;
                existingOrder.items.push(item)
            })
            return orders.filter(predicate);
        }).bind(this);
    }
    constructor(
    ) {
        //todo 应该放在这里？？
        MessageHandleManager.Current.RegistMessageHandle();
        this.orderMessageHandle = MessageHandleManager.Current.GetMessageHanle<OrderMessageHandle>("OrderMessageHandle");
        this.orderMessageHandle.OnMessage = (order: Order) => {
           mpjLog.log({
                    source:"this.orderMessageHandle.OnMessage",
                    message:"orderOnMessge",
                    extended:{order}
            });
            this.orderMessage.next(order);
        }
        this.observable = lazyLoad(scanObservable(this._unfinishedOrders, this.orderMessage, this.refreshOrder(order => !this.orderDelivered(order)), []), this.resetOrders.bind(this))
            .do(orders => { this.orders = orders; })//.do(v => console.log('refresh unfinished', v));
        this.finishedOrders = lazyLoad(scanObservable(this._finishedOrders, this.orderMessage, this.refreshOrder(order => this.orderDelivered(order)), []), this.resetFinishedOrders.bind(this))
        //.do(v => console.log('refreshFinished', v));
    }

    private async deliverOrder(order: ExpeditionOrder) {
        const result: { KitchenOrders: Order[], currentOrder: ExpeditionOrder; nextSerialNO: string } = await this.orderSvr.deliverExpeditionOrder(order);
        const filterKitchenOrderItems = R.curry((expeditionOrderItems:Array<ExpeditionOrderItem>, kitchenOrderItems:Array<Orderitem>)=>{
            const kitchenOrderItemIds=  expeditionOrderItems.map(x=>x.kitchenOrderItemId);
            return kitchenOrderItems.filter(kitchenOrderItem=>kitchenOrderItemIds.indexOf(kitchenOrderItem.id)>-1)
        })(order.items)
        result.KitchenOrders.forEach(kitchenOrder => {
            const newOrder = JSON.parse(JSON.stringify(kitchenOrder))
            newOrder.items = filterKitchenOrderItems(newOrder.items);
            FNCManager.Current.KdsMessageNotify(KdsMessageType.OrderMessage, newOrder);
            this.orderMessage.next(kitchenOrder);
        })
        const generateExpeditionOrderPrintData = (order: ExpeditionOrder) => {
            const items = order.items.map(x => {
                x.qty = x.qty - (x.refundQty || 0);
                return x;
            }).filter(x => x.qty);
            order.items = items;
            return order;
        }
        FNCManager.Current.KdsMessageNotify(KdsMessageType.ExpeditionOrderMessage, generateExpeditionOrderPrintData(result.currentOrder));
        FNCManager.Current.expeditionPrintNotify(result.currentOrder);
        ExpeditionOrderDishManage.current.resetExpeditionOrder(result.nextSerialNO)
        return result.currentOrder;
    }

    private async cancelDeliverOrder(order: ExpeditionOrder) {
        const result: { KitchenOrders: Order[], currentOrder: ExpeditionOrder; nextSerialNO: string } = await this.orderSvr.cancelDeliverExpeditionOrder(order);
        result.KitchenOrders.forEach(kitchenOrder => {
            FNCManager.Current.KdsMessageNotify(KdsMessageType.OrderMessage, kitchenOrder);
            this.orderMessage.next(kitchenOrder);
        })
        FNCManager.Current.KdsMessageNotify(KdsMessageType.ExpeditionOrderMessage, result.currentOrder);
    }

    orderClosed(order: Order) {
        return !order.items.filter(item => item.isLocalStall).some(item => item.qty != item.refundQty);
    }
    orderDelivered(order: Order) {
        // const validOrderItems = order.items.filter(item => item.isLocalStall).filter(x => x.qty != x.refundQty); //退完的直接消失
        const validOrderItems = order.items.filter(item => item.qty && item.type!="canceled" && item.isLocalStall);
        return !validOrderItems.length || validOrderItems.every(x => Boolean(x.deliverTime || x.type=="closed")) ;
    }


    private sortItems(order: Order) {
        order.items.sort((x, y) => {
            if (x.isLocalStall != y.isLocalStall) {
                return y.isLocalStall ? 1 : 0 - (x.isLocalStall ? 1 : 0);
            } else if (y.finishTime || x.finishTime) {
                const args = "finishTime"
                return (y[args] || Number.MAX_VALUE) - (x[args] || Number.MAX_VALUE);
            } else {
                return Number(x.inventoryCode) - Number(y.inventoryCode);
            }
        })
    }

    private async resetOrders() {
        const orders = await this.orderSvr.getOrders();
        orders.forEach(order => order.items = order.items.filter(item => item.refundQty || !item.pickup || item.pickup == "pickup"))
        this._unfinishedOrders.next(JSON.parse(JSON.stringify(orders)));
        return;
    }


    private orderMessageHandle: OrderMessageHandle;
    private orders: Order[] = [];
    public _unfinishedOrders = new Subject<Array<Order>>();

    private deayTime(msec?) {
        return new Promise((resolve, reject) => {
            setTimeout(function () {
                resolve(true);
            }, msec || 1000);
        })
    }


    private async toggleCartItemFinish(id: string, itemId: string) {
        const order = this.orders.find(x => x.id == id)
        if (order.items.find(x => x.id == itemId).finishTime) {
            const retOrder = await this.orderSvr.incompleteOrderItem(order.id, itemId);
            order.items[lodash.findIndex(order.items, { id: itemId })].finishTime = retOrder.items.find(x => x.id == itemId).finishTime; //先设置为0
            FNCManager.Current.KdsMessageNotify(KdsMessageType.OrderMessage, retOrder);
        } else {
            const retOrder = await this.orderSvr.completeOrderItem(order.id, itemId);
            order.items[lodash.findIndex(order.items, { id: itemId })].finishTime = retOrder.items.find(x => x.id == itemId).finishTime; //先设置为1
            FNCManager.Current.KdsMessageNotify(KdsMessageType.OrderMessage, retOrder);
        }
    }
    private async completeBranchOrderItems(orderId: string) {
        //? todo orderId应该从哪里传进来
        const itemIds = this.orders.find(x => x.id == orderId).items.filter(x => x.isLocalStall && !x.deliverTime && x.qty != x.refundQty && !x.finishTime).map(x => x.id)
        const retOrder = await this.orderSvr.completeBranchOrderItems(orderId, itemIds);
        FNCManager.Current.KdsMessageNotify(KdsMessageType.OrderMessage, retOrder);
        this.orderMessage.next(retOrder);
    }
    private async completeOrder(id: string) {
        const items = this.orders.find(x => x.id == id).items.filter(x => x.isLocalStall && !x.deliverTime).filter(x => x.qty != x.refundQty);
        if (items.some(x => !x.finishTime)) {
            throw ("还有单项未完成，不能出品！")
        }
        let orderToRefresh = await this.orderSvr.completeOrder(id, items.map(x => x.id));
        FNCManager.Current.KdsMessageNotify(KdsMessageType.OrderMessage, orderToRefresh);
        this.orderMessage.next(orderToRefresh);
    }

    private async closeOrder() {
        const order = this.orders.find(order=>order.items.filter(item=>item.isLocalStall&&item.type != "canceled").every(item=>Boolean(item.refundQty||item.deliverTime)));
        if(!order) return;
        const items = order.items.filter(item=>item.isLocalStall && item.refundQty)
        let orderToRefresh = await this.orderSvr.closeOrderItemsByItemsId(order.id, items.map(x => x.id));
        FNCManager.Current.KdsMessageNotify(KdsMessageType.OrderMessage, orderToRefresh);
        this.orderMessage.next(orderToRefresh);
    }

    private calcPriorityByElapsedTime(elapsedTime) {
        return this.orderSvr.calcPriorityByElapsedTime(elapsedTime, this.configSvr.orderConfig);
    }

    @HandleExpectionErrorUseGrowMessage()
    public async addOrder(order?) { //debug
        mockOrders[0].orderId = new Date().getTime() + "3e234"
        const result = await this.orderSvr.addOrder(order || mockOrders[0]);
        mpjLog.log({
                    source:"addOrder",
                    message:"addOrder success",
                    extended:{order,result}
        })
        const orders: Array<Order> = result.orders;
        orders.forEach(order => {
            if (order.items.some(x => Boolean(x.refundQty))) {
                PlayAudioService.current.refundOrder();
            } else {
                PlayAudioService.current.newOrder();
            }
            ExpeditionOrderDishManage.current.updateExpeditionOrderItemsByKitchenOrder(order);
            this.orderMessage.next(order);
        })
    }

    @HandleExpectionErrorUseGrowMessage()
    public async resetAllOrders() {
        await Promise.all([this.resetOrders(), this.resetFinishedOrders()]);
    }

    public async resetFinishedOrders() {
        const orders = await this.orderSvr.getFinishedOrdersByStall();
        orders.forEach(order => order.items = order.items.filter(item => !item.pickup || item.pickup == "pickup"))
        this._finishedOrders.next(orders);
        return;
    }
    public actions = {
        cancelDeliverOrder:this.cancelDeliverOrder.bind(this),
        deliverOrder: this.deliverOrder.bind(this),
        completeOrder: this.completeOrder.bind(this),
        closeOrder: this.closeOrder.bind(this),
        toggleCartItemFinish: this.toggleCartItemFinish.bind(this),
        calcPriorityByElapsedTime: this.calcPriorityByElapsedTime.bind(this),
        //以后应该和orders分开，不应该和orders一起  todo
        addOrder: this.addOrder.bind(this),
        resetAllOrders: this.resetAllOrders.bind(this),
        sortItems: this.sortItems.bind(this),
        completeBranchOrderItems: this.completeBranchOrderItems.bind(this),
        getExpeditedExpeditionOrders: this.getExpeditedExpeditionOrders.bind(this)
    }
}


export class GloablStore {
    private pagingInfo: PagingInfo;
    private _pagingInfoObservable = new Subject<PagingInfo>();
    public pagingInfoObservable: Observable<PagingInfo>;
    constructor() {
        this.pagingInfoObservable = this._pagingInfoObservable.debounceTime(100);
    }
    private notifyProduceInfo() {
        this._pagingInfoObservable.next(this.pagingInfo);
    }
    private changeProduceInfo(produceInfo: PagingInfo) {
        this.pagingInfo = produceInfo;
        this.notifyProduceInfo();
    }
    public actions = {
        changeProduceInfo: this.changeProduceInfo.bind(this)
    }
}

export class System {
    nodeServices: Array<NodeService> = [];
    //todo 枚举类型
    status: string;
}

export class SystemStore {
    private sysSvr: SysSvr = new SysSvr()
    public systemObservable = new Subject<System>();
    private system: System = new System();
    private changeCheckedNodeService(nodeService: NodeService) {
        const res = this.system.nodeServices.find(x => x.name == nodeService.name);
        res ? res.status = nodeService.status : this.system.nodeServices.push(nodeService)
        this.notify();
    }

    constructor() {
        // this.checkedNodeServices = this.sysSvr.getNodeSvrList();
        this.sysSvr.DependentServiceChangedEvent = (sender) => {
            this.changeCheckedNodeService(sender);
        }
    }

    private async startCheckEnv(LDCIp?) {
        // this.changeCheckedNodeService(await this.sysSvr.checkLDC())
        // this.changeCheckedNodeService(await this.sysSvr.checkFNS())
        this.changeCheckedNodeService(await this.sysSvr.checkFBS())

    }
    private async fncConnect() {
        const LDCIp = this.sysSvr.LDCConfig.ip;
        if (LDCIp && !FNCManager.Current.IsConnect) {
            const fncManager = await FNCManager.Current.Connect(LDCIp)
            FNCManager.Current.FNC.RegistMessageHandler("kdsMessage", (message) => {
                MessageHandleManager.Current.ReceivedMessage(message);
            })
        }
    }
    private async doBackgroundTask() {
        this.sysSvr.checkFBS()
    }
    private checkFbsOnce() {
        return this.sysSvr.checkFbsOnce();
    }
    private checkLDCOnce() {
        return this.sysSvr.checkLDCOnce();
    }
    private async findLDC(LDCIp, config?) {
        try {
            const LDCConfig = await this.sysSvr.getLDCConfig(LDCIp, config);
            LDCConfig.ip = LDCIp;
            this.sysSvr.LDCConfig = LDCConfig;
            //这时候就知道去链接FNS
            this.fncConnect()
            return LDCConfig;
        } catch (error) {
            throw (error);
        }
    }
    private notify() {
        this.systemObservable.next(this.system);
    }
    private reNotify() {
        this.notify()
    }
    private setAdsAbout() {
        return this.sysSvr.setAdsAbout();
    }
    public actions = {
        findLDC: this.findLDC.bind(this),
        fncConnect: this.fncConnect.bind(this),
        doBackgroundTask: this.doBackgroundTask.bind(this),
        reNotify: this.reNotify.bind(this),
        setAdsAbout: this.setAdsAbout.bind(this),
        checkFbsOnce: this.checkFbsOnce.bind(this),
        checkLDCOnce: this.checkLDCOnce.bind(this)
    }
}




@Injectable()
export class AppStore {


    constructor() {
    }

    //todo 写成容易更改的
    public gloablStore = new GloablStore();
    public orderStore = new OrderStore();
    public systemStore = new SystemStore();
}